using System;
using System.Diagnostics;
using System.Threading;

namespace Domin8
{
    /// <summary>
    /// Provides scoped, thread static storage for a type.
    /// </summary>
    /// <remarks>
    /// From the September '06 issue of MSDN Magazine, .NET Matters 
    /// column (http://msdn.microsoft.com/msdnmag/issues/06/09/NETMatters/)
    /// </remarks>
    /// <typeparam name="T">Type of the scoped data</typeparam>
    public sealed class Scope<T> : IDisposable where T : class
    {
        private bool _disposed, _ownsInstance;
        private T _instance;
        private Scope<T> _parent;

        [ThreadStatic]
        private static Scope<T> _head;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="instance">Instance of the scoped data</param>
        public Scope(T instance) : this(instance, true) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="instance">Instance of the scoped data</param>
        /// <param name="ownsInstance">If true, allows Scope instance to 
        /// dispose of the instance when it leaves scope.</param>
        public Scope(T instance, bool ownsInstance)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");
            _instance = instance;
            _ownsInstance = ownsInstance;
            Thread.BeginThreadAffinity();
            _parent = _head;
            _head = this;
        }

        /// <summary>
        /// Current value in scope
        /// </summary>
        public static T Current
        {
            get { return _head != null ? _head._instance : null; }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, 
        /// or resetting unmanaged resources.  
        /// </summary>
        public void Dispose()
        {
            if (!_disposed)
            {
                _disposed = true;
                Debug.Assert(this == _head, "Disposed out of order.");
                _head = _parent;
                Thread.EndThreadAffinity();
                if (_ownsInstance)
                {
                    IDisposable disposable = _instance as IDisposable;
                    if (disposable != null) disposable.Dispose();
                }
            }
        }
    }
}
